package net.piggy.gateway;

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.AttributeKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URI;

public class HttpServer {

    private final static Logger logger = LoggerFactory.getLogger(HttpServer.class);

    public static void main(String[] args) throws InterruptedException, IOException {
        EventLoopGroup boss = new NioEventLoopGroup(1);
        EventLoopGroup work = new NioEventLoopGroup(4);
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(boss, work)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 128)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel channel) {

                        ChannelPipeline pipeline = channel.pipeline();
//                        pipeline.addLast(new LoggingHandler(LogLevel.DEBUG));
                        pipeline.addLast(new HttpServerCodec());
                        pipeline.addLast(new HttpObjectAggregator(65536));

                        pipeline.addLast(new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                                logger.info("HttpServer.channelInactive -> {}", ctx.channel().hashCode());
                                ctx.close();
                            }

                            @Override
                            public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                logger.info("channelActive.channel.hashcode -> {}", ctx.channel().hashCode());
                                super.channelActive(ctx);
                            }

                            @Override
                            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                                ctx.close();
                                cause.printStackTrace();
                            }

                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                if (msg instanceof FullHttpRequest) {
                                    logger.info("channelRead.channel.hashcode -> {}", ctx.channel().hashCode());
                                    FullHttpRequest request = (FullHttpRequest) msg;
                                    String connection = request.headers().get(HttpHeaderNames.CONNECTION);
                                    String upgrade = request.headers().get(HttpHeaderNames.UPGRADE);
                                    if (upgrade == null) {
                                        // http
                                        if( "keep-alive".equals(connection)){
                                            HttpClientSecond.send(request, ctx.channel());
                                        }
                                    } else {
                                        // websocket
                                        if("Upgrade".equals(connection) && "websocket".equals(upgrade)){
                                            WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(request.uri(), null, true);

                                            WebSocketServerHandshaker obj = wsFactory.newHandshaker(request);
                                            ChannelFuture handshake = obj.handshake(ctx.channel(), request);

                                            handshake.addListener((ChannelFutureListener) future -> {
                                                if(future.isSuccess()){
                                                    EventLoopGroup group = new NioEventLoopGroup();

                                                    WebSocketClientHandshaker shaker = WebSocketClientHandshakerFactory
                                                            .newHandshaker(new URI("ws://" + Config.getConfigParam().getTargetIp() + ":" + Config.getConfigParam().getTargetPort() + request.uri()),
                                                                    WebSocketVersion.V13, "piggy", true, request.headers());

                                                    WebSocketClientHandler handler = new WebSocketClientHandler(shaker);
                                                    Bootstrap client = new Bootstrap();
                                                    client.group(group)
                                                            .channel(NioSocketChannel.class)
                                                            .handler(new ChannelInitializer<SocketChannel>() {
                                                                @Override
                                                                protected void initChannel(SocketChannel ch) {
                                                                    ChannelPipeline p = ch.pipeline();
                                                                    p.addLast(new HttpClientCodec());
                                                                    p.addLast(new HttpObjectAggregator(65536));
                                                                    p.addLast(WebSocketClientCompressionHandler.INSTANCE);
                                                                    p.addLast(handler);
                                                                }
                                                            });

                                                    ChannelFuture channelFuture = client.connect(Config.getConfigParam().getTargetIp(), Config.getConfigParam().getTargetPort()).sync();
                                                    handler.handshakeFuture().sync();

                                                    Channel ch = channelFuture.channel();
                                                    future.channel().attr(AttributeKey.valueOf("channel")).set(ch);
                                                    ch.attr(AttributeKey.valueOf("channel")).set(future.channel());
                                                }
                                            });
                                        }
                                    }
                                } else if (msg instanceof WebSocketFrame) {
                                    if (msg instanceof TextWebSocketFrame){
                                        TextWebSocketFrame frame = (TextWebSocketFrame) msg;
                                        String text = frame.text();
                                        Channel channel = (Channel) ctx.channel().attr(AttributeKey.valueOf("channel")).get();
                                        channel.writeAndFlush(new TextWebSocketFrame(text));
                                    } else if(msg instanceof BinaryWebSocketFrame){
                                        BinaryWebSocketFrame frame = (BinaryWebSocketFrame) msg;
                                    }
                                }
                            }
                        });
                    }
                });

        Channel channel = bootstrap.bind(Config.getConfigParam().getIp(), Config.getConfigParam().getPort()).sync().channel();
        ChannelFuture channelFuture = channel.closeFuture().sync();
        channelFuture.addListener((ChannelFutureListener) listener -> logger.info("server close"));
    }
}
